﻿namespace EFS.Common.Serialization
{
	public class XmlReader
	{
		#region Composition

		// document
		System.Xml.Linq.XDocument xmlDoc = null;

		// writter
		private System.Xml.XmlReader xmlReader = null;

		#endregion

		#region Constructors

		/// <summary>
		/// Constructor
		/// </summary>
		public XmlReader(System.String xml)
		{
			// create new document
			xmlDoc = System.Xml.Linq.XDocument.Parse(xml);

			// create writer
			xmlReader = xmlDoc.CreateReader();

			// read
			xmlReader.Read();
		}

		#endregion

		#region Read

		/// <summary>
		/// Read array of items
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public void Read<T>(System.String startElement, System.String itemElement, ref T[] array) where T : ISerializable
		{
			// check
			EFS.Common.Verify.String(startElement, "startElement");
			EFS.Common.Verify.String(itemElement, "itemElement");

			// create list
			System.Collections.Generic.List<T> collection = new System.Collections.Generic.List<T>();

			// check
			if (xmlReader.Name != startElement)
			{
				throw new System.InvalidOperationException("Unexpected XML format");
			}

			// check emppty
			if (xmlReader.IsEmptyElement)
			{
				xmlReader.Read();
			}
			else
			{
				// get constructor
				System.Reflection.ConstructorInfo contructorInfo = typeof(T).GetConstructor(new System.Type[0]);
				EFS.Common.Verify.Reference(contructorInfo, "contructorInfo");

				// read start
				xmlReader.ReadStartElement(startElement);

				// move to content
				if (xmlReader.NodeType == System.Xml.XmlNodeType.Element)
				{
					do
					{
						// create instance
						T t = (T)contructorInfo.Invoke(new System.Object[0]);
						EFS.Common.Verify.Reference(t, "Failed to call factory");

						// create start
						xmlReader.ReadStartElement(itemElement);

						// serialize
						t.Deserialize(this);

						// create end
						xmlReader.ReadEndElement();

						// add to collection
						collection.Add(t);
					}
					while (xmlReader.Name == itemElement);
				}

				// read end
				xmlReader.ReadEndElement();
			}

			// get array
			array = collection.ToArray();
		}

		/// <summary>
		/// Factory delegate for reader
		/// </summary>
		/// <returns></returns>
		public delegate ISerializable Factory();

		/// <summary>
		/// Read array of items
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		public void Read<T>(System.String startElement, System.String itemElement, Factory factory = null) where T : ISerializable
		{
			// check
			EFS.Common.Verify.String(startElement, "startElement");
			EFS.Common.Verify.String(itemElement, "itemElement");
			EFS.Common.Verify.Reference(factory, "factory");

			// create list
			System.Collections.Generic.List<T> collection = new System.Collections.Generic.List<T>();
			// check
			if (xmlReader.Name != startElement)
			{
				throw new System.InvalidOperationException("Unexpected XML format");
			}

			// check emppty
			if (xmlReader.IsEmptyElement)
			{
				xmlReader.Read();
			}
			else
			{
				// read start
				xmlReader.ReadStartElement(startElement);

				// move to content
				if (xmlReader.NodeType == System.Xml.XmlNodeType.Element)
				{
					do
					{
						// make t
						T t = (T)factory();
						EFS.Common.Verify.Reference(t, "Failed to call factory");

						// create start
						xmlReader.ReadStartElement(itemElement);

						// serialize
						t.Deserialize(this);

						// create end
						xmlReader.ReadEndElement();

						// add to collection
						collection.Add(t);
					}
					while (xmlReader.Name == itemElement);
				}

				// read end
				xmlReader.ReadEndElement();
			}
		}

		/// <summary>
		/// Read & create one object
		/// </summary>
		/// <param name="startElement"></param>
		/// <returns></returns>
		public ISerializable ReadPolymorphic(System.String startElement)
		{
			// check
			EFS.Common.Verify.String(startElement, "startElement");

			// check
			if (xmlReader.Name != startElement)
			{
				throw new System.InvalidOperationException("Unexpected XML format");
			}

			// check null
			System.String isNull = xmlReader.GetAttribute("IsNull");
			if (EFS.Common.Verify.String(isNull) && "true" == isNull.ToLower())
			{
				// move to next
				xmlReader.Read();

				// null
				return null;
			}

			// read class
			System.String className = xmlReader.GetAttribute("Class");

			// get type
			System.Type t = System.Type.GetType(className);
			if (null == t)
			{
				throw new System.Exception(System.String.Format("Could not locate class {0}", className));
			}

			// check type
			if (!typeof(ISerializable).IsAssignableFrom(t))
			{
				throw new System.Exception(System.String.Format("Class {0} must be ISerializable", className));
			}

			// get constructor
			System.Reflection.ConstructorInfo contructorInfo = t.GetConstructor(new System.Type[0]);
			EFS.Common.Verify.Reference(contructorInfo, "contructorInfo");

			// create instance
			ISerializable obj = (ISerializable)contructorInfo.Invoke(new System.Object[0]);

			// check empty
			if (!xmlReader.IsEmptyElement)
			{
				// read start
				xmlReader.ReadStartElement();

				// serialize
				obj.Deserialize(this);

				// end
				xmlReader.ReadEndElement();
			}

			// done
			return obj;
		}

		/// <summary>
		/// Read
		/// </summary>
		/// <param name="startElement"></param>
		/// <param name="s"></param>
		public void Read(System.String startElement, ISerializable s)
		{
			// check
			EFS.Common.Verify.String(startElement, "startElement");
			EFS.Common.Verify.Reference(s, "s");

			// check
			if (xmlReader.Name != startElement)
			{
				throw new System.InvalidOperationException("Unexpected XML format expect startElement:" + startElement + " , have xmlReader.Name:" + xmlReader.Name);
			}

			// check emppty
			if (xmlReader.IsEmptyElement)
			{
				xmlReader.Read();
			}
			else
			{
				// read start
				xmlReader.ReadStartElement(startElement);

				// deserialize
				s.Deserialize(this);

				// read end
				xmlReader.ReadEndElement();
			}
		}

		/// Value
		public void ReadValue(System.String itemElement, EFS.Common.Values.Interfaces.Value value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");

			// read start
			if (xmlReader.Name == itemElement)
			{
				// start 
				xmlReader.ReadStartElement(itemElement);

				// use serialization
				value.Deserialize(this);

				// end
				xmlReader.ReadEndElement();
			}
		}

		/// String
		public System.String Read(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return xmlReader.ReadElementContentAsString(itemElement, "");
		}

		/// Char
		public System.Char? ReadChar(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Char.From(Read(itemElement));
		}

		/// Guid
		public System.Guid? ReadGuid(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Guid.From(Read(itemElement));
		}

		/// DateTime
		public System.DateTime? ReadDateTime(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Date.From(Read(itemElement));
		}

		/// Int16
		public System.Int16? ReadInt16(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Int16.From(Read(itemElement));
		}

		/// Int32
		public System.Int32? ReadInt32(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Int32.From(Read(itemElement));
		}

		/// Int64
		public System.Int64? ReadInt64(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Int64.From(Read(itemElement));
		}

		/// Double
		public System.Double? ReadDouble(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Double.From(Read(itemElement));
		}

		/// Decimal
		public System.Decimal? ReadDecimal(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Decimal.From(Read(itemElement));
		}

		/// Boolean
		public System.Boolean? ReadBoolean(System.String itemElement)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			return EFS.Common.Functions.Boolean.From(Read(itemElement));
		}

		/// String
		public void Read(System.String itemElement, ref System.String value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			value = Read(itemElement);
		}

		/// Guid
		public void Read(System.String itemElement, ref System.Guid value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			System.Guid? nValue = ReadGuid(itemElement);
			if (null != nValue)
			{
				value = nValue.Value;
			}
		}

		/// DateTime
		public void Read(System.String itemElement, ref System.DateTime value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			System.DateTime? nValue = ReadDateTime(itemElement);
			if (null != nValue)
			{
				value = nValue.Value;
			}
		}

		/// Int32
		public void Read(System.String itemElement, ref System.Int32 value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			System.Int32? nValue = ReadInt32(itemElement);
			if (null != nValue)
			{
				value = nValue.Value;
			}
		}

		/// Int64
		public void Read(System.String itemElement, ref System.Int64 value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			System.Int64? nValue = ReadInt64(itemElement);
			if (null != nValue)
			{
				value = nValue.Value;
			}
		}

		/// Int64
		public void Read(System.String itemElement, ref System.Boolean value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			System.Boolean? nValue = ReadBoolean(itemElement);
			if (null != nValue)
			{
				value = nValue.Value;
			}
		}

		/// Int64
		public void Read(System.String itemElement, ref System.Double value)
		{
			EFS.Common.Verify.String(itemElement, "itemElement");
			System.Double? nValue = ReadDouble(itemElement);
			if (null != nValue)
			{
				value = nValue.Value;
			}
		}

		/// <summary>
		/// Read array of string
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="list"></param>
		public void Read(System.String startElement, System.String itemElement, ref System.String[] array)
		{
			// check
			EFS.Common.Verify.String(startElement, "startElement");
			EFS.Common.Verify.String(itemElement, "itemElement");

			// create list
			System.Collections.Generic.List<System.String> collection = new System.Collections.Generic.List<System.String>();

			// check
			if (xmlReader.Name != startElement)
			{
				throw new System.InvalidOperationException("Unexpected XML format");
			}

			// check emppty
			if (xmlReader.IsEmptyElement)
			{
				xmlReader.Read();
			}
			else
			{
				// read
				xmlReader.ReadStartElement(startElement);

				// check
				if (xmlReader.NodeType == System.Xml.XmlNodeType.Element)
				{
					do
					{
						// get item
						collection.Add(Read(itemElement));
					}
					while (xmlReader.Name == itemElement);
				}

				// end
				xmlReader.ReadEndElement();
			}

			// get array
			array = collection.ToArray();
		}
		#endregion

		#region Tools

		/// <summary>
		/// Get current node name
		/// </summary>
		public System.String CurrentNodeName
		{
			get { return xmlReader.Name; }
		}

		/// <summary>
		/// Close
		/// </summary>
		public void Close()
		{
			xmlReader.Close();
		}

		#endregion
	}

}